//-------------------------------------------------------------------
//	Copyright (c) 2010-2012 Zhirnov Andrey
//	This file is part of the "UXGEN" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#ifndef _UX_MATRIX_STACK_H
#define _UX_MATRIX_STACK_H

#include "CStateManager.h"

namespace Engine
{
namespace GLSystems
{
#ifdef UX_MSTACK_CACHE_DISABLE
#	define MSTACK_CACHE( _check )
#else
#	define MSTACK_CACHE( _check )	_check
#endif


	//
	// Matrix Stack
	//

	class CMatrixStack
	{
	private:
		enum EMaterixType
		{
			MODEL_VIEW			= 0,
			VIEW_PROJECTION,
			MODEL_VIEW_PROJECTION,
			NORMAL,

			MODEL_VIEW_INVERSE,
			VIEW_PROJECTION_INVERSE,
			MODEL_VIEW_PROJECTION_INVERSE,
			NORMAL_INVERSE,

			_COUNT
		};

		typedef array< mat4 >				matrix_array_t;
		typedef stack< mat4 >				matrix_stack_t;
		typedef array< matrix_stack_t >		matrix_stack_arr_t;

	private:
		matrix_stack_arr_t		_aMatrices;
		// cache //
		//matrix_array_t			_aInvMatrices;
		mat4					_sMVMatrix,
								_sVPMatrix,
								_sMVPMatrix;
		mat4					_sMVMatrixInverse,
								_sVPMatrixInverse,
								_sMVPMatrixInverse;
		mat3					_sNormalMatrix,
								_sNormalMatrixInverse;
		
		TBitfield<uint,_COUNT>	_sMatricesChanged;
		
	private:
		void _OnMatrixChanged(e_transform::type eType);

		template <uint Count>
		void _OnMatrixChangedA(e_transform::type eType, ...);

	public:
		CMatrixStack();
		~CMatrixStack();

		void Reset();

		void MultMatrix(const mat4 &sMatrix, e_transform::type eState);
		void SetMatrix(const mat4 &sMatrix, e_transform::type eState);

		const mat4 & GetMatrix(e_transform::type eState) const;
		const mat4 & GetMatrixMVP();
		const mat4 & GetMatrixMV();
		const mat4 & GetMatrixVP();
		const mat3 & GetMatrixNormal();
		
		//const mat4 & GetMatrixInverse(e_transform::type eState) const;
		const mat4 & GetMatrixMVPInverse();
		const mat4 & GetMatrixMVInverse();
		const mat4 & GetMatrixVPInverse();
		const mat3 & GetMatrixNormalInverse();

		void PushMatricesMVP();
		void PopMatricesMVP();
		void PushMatrix(e_transform::type eState);
		void PopMatrix(e_transform::type eState);

		// set cache matrices
		void SetMatrixMVP(const mat4 &sMatrix);
		void SetMatrixMV(const mat4 &sMatrix);
		void SetMatrixVP(const mat4 &sMatrix);
		void SetMatrixNormal(const mat3 &sMatrix);

		void SetMatrixMVP(const mat4 &sModel, const mat4 &sView, const mat4 &sProj, const mat4 &sMVP);
		void SetMatrixMV(const mat4 &sModel, const mat4 &sView, const mat4 &sMV);
		void SetMatrixVP(const mat4 &sView, const mat4 &sProj, const mat4 &sVP);
	};

	

/*
=================================================
	
=================================================
*/
	inline CMatrixStack::CMatrixStack(): _sMatricesChanged(0)
	{
		_aMatrices.Resize( e_transform::_COUNT, false );
		_aMatrices[e_transform::VIEW].Resize(16);
		_aMatrices[e_transform::MODEL].Resize(32);
		_aMatrices[e_transform::PROJECTION].Resize(16);

		_aMatrices[e_transform::VIEW].SetDefValue( mat4::Identity() );
		_aMatrices[e_transform::MODEL].SetDefValue( mat4::Identity() );
		_aMatrices[e_transform::PROJECTION].SetDefValue( mat4::Identity() );

		for (uint i = e_transform::TEXTURE_0; i < _aMatrices.Count(); ++i)
		{
			_aMatrices[i].Resize(4);
			_aMatrices[i].SetDefValue( mat4::Identity() );
		}
	}
	
/*
=================================================
	
=================================================
*/
	inline CMatrixStack::~CMatrixStack()
	{
		_aMatrices.Clear();
	}
	
/*
=================================================
	
=================================================
*/
	inline void CMatrixStack::Reset()
	{
		_sMatricesChanged = 0;

		for (uint i = 0; i < _aMatrices.Count(); ++i)
			_aMatrices[i].Clear();
	}

/*
=================================================
	
=================================================
*/
	inline void CMatrixStack::MultMatrix(const mat4 &sMatrix, e_transform::type eState)
	{
		ASSERT( eState < e_transform::_COUNT );

		_aMatrices[eState].Get() *= sMatrix;
		_OnMatrixChanged( eState );
	}
	
/*
=================================================
	
=================================================
*/
	inline void CMatrixStack::SetMatrix(const mat4 &sMatrix, e_transform::type eState)
	{
		ASSERT( eState < e_transform::_COUNT );

		_aMatrices[eState].Set( sMatrix );
		_OnMatrixChanged( eState );
	}
/*
=================================================
	
=================================================
*/
	inline void CMatrixStack::PushMatrix(e_transform::type eState)
	{
		_aMatrices[eState].Push();
	}
	
	inline void CMatrixStack::PushMatricesMVP()
	{
		_aMatrices[e_transform::MODEL].Push();
		_aMatrices[e_transform::VIEW].Push();
		_aMatrices[e_transform::PROJECTION].Push();
	}

/*
=================================================
	
=================================================
*/
	inline void CMatrixStack::PopMatrix(e_transform::type eState)
	{
		_aMatrices[eState].Pop();
		_OnMatrixChanged( eState );
	}
	
	inline void CMatrixStack::PopMatricesMVP()
	{
		_aMatrices[e_transform::MODEL].Pop();
		_aMatrices[e_transform::VIEW].Pop();
		_aMatrices[e_transform::PROJECTION].Pop();
		_OnMatrixChangedA<3>( e_transform::MODEL, e_transform::VIEW, e_transform::PROJECTION );
	}
	
/*
=================================================
	
=================================================
*/
	inline void CMatrixStack::_OnMatrixChanged(e_transform::type eType)
	{
		MSTACK_CACHE
		(
			bool const	b_mv	= (eType == e_transform::MODEL) | (eType == e_transform::VIEW);
			bool const	b_vp	= (eType == e_transform::VIEW)  | (eType == e_transform::PROJECTION);
			bool const	b_mvp	= b_mv | b_vp;
			uint		bits	= 0;

			bits |= b_mv  << MODEL_VIEW;
			bits |= b_vp  << VIEW_PROJECTION;
			bits |= b_mvp << MODEL_VIEW_PROJECTION;
			bits |= b_mv  << NORMAL;
		
			bits |= b_mv  << MODEL_VIEW_INVERSE;
			bits |= b_vp  << VIEW_PROJECTION_INVERSE;
			bits |= b_mvp << MODEL_VIEW_PROJECTION_INVERSE;
			bits |= b_mv  << NORMAL_INVERSE;

			_sMatricesChanged |= bits;
		);
	}
	
/*
=================================================
	
=================================================
*/
	template <uint Count>
	inline void CMatrixStack::_OnMatrixChangedA(e_transform::type eType, ...)
	{
		MSTACK_CACHE
		(
			struct TIteration
			{
				TVAarg< e_transform::type >		args;
				bool							bMV;
				bool 							bVP;
				///
				TIteration(e_transform::type &eType):
					args(eType)
				{
					bMV	= (eType == e_transform::MODEL) | (eType == e_transform::VIEW);
					bVP	= (eType == e_transform::VIEW)  | (eType == e_transform::PROJECTION);
				}

				void operator () ()
				{
					e_transform::type	e_type = args.Get();

					bMV	|= (e_type == e_transform::MODEL) | (e_type == e_transform::VIEW);
					bVP	|= (e_type == e_transform::VIEW)  | (e_type == e_transform::PROJECTION);
				}
			};


			TIteration	s_iter( eType );

			Unroll< Count >( s_iter );
		
			bool const	b_mv	= s_iter.bMV;
			bool const	b_vp	= s_iter.bVP;
			bool const	b_mvp	= b_mv | b_vp;
			uint		bits	= 0;

			bits |= b_mv  << MODEL_VIEW;
			bits |= b_vp  << VIEW_PROJECTION;
			bits |= b_mvp << MODEL_VIEW_PROJECTION;
			bits |= b_mv  << NORMAL;
		
			bits |= b_mv  << MODEL_VIEW_INVERSE;
			bits |= b_vp  << VIEW_PROJECTION_INVERSE;
			bits |= b_mvp << MODEL_VIEW_PROJECTION_INVERSE;
			bits |= b_mv  << NORMAL_INVERSE;

			_sMatricesChanged |= bits;
		);
	}

/*
=================================================
	
=================================================
*/
	inline const mat4 & CMatrixStack::GetMatrix(e_transform::type eState) const
	{
		return _aMatrices[eState].Get();
	}
	
/*
=================================================
	
=================================================
*/
	inline const mat4 & CMatrixStack::GetMatrixMVP()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( MODEL_VIEW_PROJECTION ) ) )
		{
			_sMatricesChanged.Reset( MODEL_VIEW_PROJECTION );
			_sMVPMatrix = GetMatrixVP() * _aMatrices[e_transform::MODEL].Get();
		}
		return _sMVPMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline const mat4 & CMatrixStack::GetMatrixMV()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( MODEL_VIEW ) ) )
		{
			_sMatricesChanged.Reset( MODEL_VIEW );
			_sMVMatrix = _aMatrices[e_transform::VIEW].Get() *
						 _aMatrices[e_transform::MODEL].Get();
		}
		return _sMVMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline const mat4 & CMatrixStack::GetMatrixVP()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( VIEW_PROJECTION ) ) )
		{
			_sMatricesChanged.Reset( VIEW_PROJECTION );
			_sVPMatrix = _aMatrices[e_transform::PROJECTION].Get() *
						 _aMatrices[e_transform::VIEW].Get();
		}
		return _sVPMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline const mat3 & CMatrixStack::GetMatrixNormal()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( NORMAL ) ) )
		{
			_sMatricesChanged.Reset( NORMAL );
			_sNormalMatrix = GetMatrix(e_transform::MODEL).To<mat3>().Inverse().Transpose();
		}
		return _sNormalMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline const mat4 & CMatrixStack::GetMatrixMVPInverse()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( MODEL_VIEW_PROJECTION_INVERSE ) ) )
		{
			_sMatricesChanged.Reset( MODEL_VIEW_PROJECTION_INVERSE );
			_sMVPMatrixInverse = GetMatrixMVP().Inverse();
		}
		return _sMVPMatrixInverse;
	}

/*
=================================================
	
=================================================
*/
	inline const mat4 & CMatrixStack::GetMatrixMVInverse()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( MODEL_VIEW_INVERSE ) ) )
		{
			_sMatricesChanged.Reset( MODEL_VIEW_INVERSE );
			_sMVMatrixInverse = GetMatrixMV().Inverse();
		}
		return _sMVMatrixInverse;
	}

/*
=================================================
	
=================================================
*/
	inline const mat4 & CMatrixStack::GetMatrixVPInverse()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( VIEW_PROJECTION_INVERSE ) ) )
		{
			_sMatricesChanged.Reset( VIEW_PROJECTION_INVERSE );
			_sVPMatrixInverse = GetMatrixVP().Inverse();
		}
		return _sVPMatrixInverse;
	}

/*
=================================================
	
=================================================
*/
	inline const mat3 & CMatrixStack::GetMatrixNormalInverse()
	{
		MSTACK_CACHE(
		if ( _sMatricesChanged.Get( NORMAL_INVERSE ) ) )
		{
			_sMatricesChanged.Reset( NORMAL_INVERSE );
			_sNormalMatrixInverse = GetMatrixNormal().Inverse();
		}
		return _sNormalMatrixInverse;
	}

/*
=================================================
	
=================================================
*/
	inline void CMatrixStack::SetMatrixMVP(const mat4 &sMatrix)
	{
		// M or V or P matrices must be changed
		//_OnMatrixChangedA<3>( e_transform::MODEL, e_transform::VIEW, e_transform::PROJECTION );

		_sMatricesChanged.Reset( MODEL_VIEW_PROJECTION );
		_sMVPMatrix = sMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline void CMatrixStack::SetMatrixMV(const mat4 &sMatrix)
	{
		// M or V matrices must be changed
		//_OnMatrixChangedA<2>( e_transform::MODEL, e_transform::VIEW );

		_sMatricesChanged.Reset( MODEL_VIEW );
		_sMVMatrix = sMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline void CMatrixStack::SetMatrixVP(const mat4 &sMatrix)
	{
		// V or P matrices must be changed
		//_OnMatrixChangedA<2>( e_transform::VIEW, e_transform::PROJECTION );

		_sMatricesChanged.Reset( VIEW_PROJECTION );
		_sVPMatrix = sMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline void CMatrixStack::SetMatrixNormal(const mat3 &sMatrix)
	{
		_sMatricesChanged.Reset( NORMAL );
		_sNormalMatrix = sMatrix;
	}
	
/*
=================================================
	
=================================================
*/
	inline void CMatrixStack::SetMatrixMVP(const mat4 &sModel, const mat4 &sView, const mat4 &sProj, const mat4 &sMVP)
	{
		_aMatrices[ e_transform::MODEL ].Set( sModel );
		_aMatrices[ e_transform::VIEW  ].Set( sView  );
		_aMatrices[ e_transform::PROJECTION ].Set( sProj );
		_OnMatrixChangedA<3>( e_transform::MODEL, e_transform::VIEW, e_transform::PROJECTION );
		SetMatrixMVP( sMVP );
	}
	
/*
=================================================
	
=================================================
*/
	inline void CMatrixStack::SetMatrixMV(const mat4 &sModel, const mat4 &sView, const mat4 &sMV)
	{
		_aMatrices[ e_transform::MODEL ].Set( sModel );
		_aMatrices[ e_transform::VIEW  ].Set( sView  );
		_OnMatrixChangedA<2>( e_transform::MODEL, e_transform::VIEW );
		SetMatrixMV( sMV );
	}
	
/*
=================================================
	
=================================================
*/
	inline void CMatrixStack::SetMatrixVP(const mat4 &sView, const mat4 &sProj, const mat4 &sVP)
	{
		_aMatrices[ e_transform::VIEW  ].Set( sView  );
		_aMatrices[ e_transform::PROJECTION ].Set( sProj );
		_OnMatrixChangedA<2>( e_transform::VIEW, e_transform::PROJECTION );
		SetMatrixVP( sVP );
	}

//-------------------------------------------------------------------

}	// GLSystems
}	// Engine

#endif	// _UX_MATRIX_STACK_H